package net.alche.fa.core;

import net.alche.fa.core.model.FaCoreConf;
import java.util.Map;
import java.util.HashMap;
import net.alche.fa.core.model.FaValue;
import net.alche.fa.core.model.VarItem;
import net.alche.fa.core.model.BreakPoint;
import net.alche.fa.biz.model.CtrlNode;
import java.util.List;
import net.alche.fa.core.doing.AbstractDoing;
import net.alche.fa.core.doing.SqlDo;
import net.alche.fa.core.doing.FaDo;
import net.alche.al.util.AC;
import cn.hutool.core.io.FileUtil;
import net.alche.fa.core.parse.FavaParseLogicV2;
import java.util.Map.Entry;

import javax.lang.model.util.ElementFilter;

import java.util.ArrayList;
import java.util.Set;
import net.alche.al.util.alches.ToolClass;
import net.alche.fa.core.doing.FaAnnoDoing;
import java.util.Arrays;
// import net.alche.al.util.SpringUtil;
import net.alche.fa.core.model.FaParam;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

// FaPlatform
@SuppressWarnings("all")
public class FaPlatform {
  public static final Logger logger = LoggerFactory.getLogger(FaPlatform.class);
  public static final int BREAK_STEP_CONTINUE = 1; // 继续走
  public static final int BREAK_STEP_OVER = 2; // 下一行
  public static final int BREAK_STOP = 4; // 结束运行
  FaCoreConf conf;
  Map<String, FaClassConf> classMap = new HashMap<>(); // fa对象定义
  Map<String, String> objPath = null; // <原生ClassName, 原生包路径+ClassName>
  Map<String, Class> objClasses = null; // <原生ClassName, 原生Class对象>
  Map<String, Class> objClassesTmp = null;
  private Map<String, FaValue> staticFa = new HashMap<>(); // <原生类名称, 静态对象FaValue>
  Map<String, BreakPoint> bpMap = new HashMap<>(); // 断点信息
  Map<String, FaClass> sprBeanMap = new HashMap<>(); // spring-Bean对象
  Map<String, Object> sprJavaMap = new HashMap<>(); // spring-java-Bean对象
  CtrlNode rootNode; // web请求根路径
  List<AbstractDoing> doingList = null;
  SqlDo sqlDo = new SqlDo();
  FaDo faDo = new FaDo();
  // 泛型对象
  String[] widlyTypes = new String[] { "T", "E", "K", "V", "N", "?", "S", "U", "A", "R" };
  Map<String, Boolean> breakPoints;
  SageStone debugSage; // 当前debuging的Sage对象
  long breakThreadId = 0;
  int breakPointCtrl = BREAK_STEP_CONTINUE;
  boolean isSleeping = false;
  boolean isDebug = true;
  public Object fatis; // sdd-fatis
  public List<VarItem> extendsObjectList = null;

  public FaPlatform() {
  }

  // 开始方法
  public boolean startFa() throws FaException {
    this.sqlDo.setPlat(this);
    this.faDo.setPlat(this);
    this.faDo.initStaticPath();
    this.faDo.initFaConf();
    this.initDoingList();
    this.fixFa();
    this.initClassConf();
    return true;
  }

  // start-default
  public boolean startFaDefault(String rootPath) throws FaException {
    logger.info("fa.plat.rootPath = " + rootPath);
    if (!FileUtil.exist(rootPath)) {
      logger.error("fa.plat.rootPath not exit: " + rootPath);
      return false;
    }
    FavaParseLogicV2 v2 = new FavaParseLogicV2();
    v2.setRootPath(rootPath);
    Map<String, FaClassConf> map = v2.parse(null);
    this.classMap = map;
    return this.startFa();
  }

  public SageStone newSageStome(String className) throws FaException {
    FaClassConf faClassConf = this.getClassMap().get(className);
    if (faClassConf == null) {
      throw new FaException(FaException.NO_SUCH_CLASS, className);
    }
    FaClass fac = faClassConf.instance(null);
    SageStone st = fac.getThisSage();
    return st;
  }

  public SageStone newSageStome(String className, Object... pars) throws FaException {
    FaClassConf faClassConf = this.getClassMap().get(className);
    if (faClassConf == null) {
      throw new FaException(FaException.NO_SUCH_CLASS, className);
    }
    FaClass fac = faClassConf.instance(null);
    SageStone st = fac.getThisSage();
    return st;
  }

  public FaClass instance(String className, CodeLevel lvl) throws FaException {
    FaClassConf faClassConf = this.classMap.get(className);
    if (faClassConf != null) {
      faClassConf.setPlat(this);
      return faClassConf.instance(lvl);
    }
    return null;
  }

  // 是否为Sage对象
  public boolean isSageName(String pojoName) {
    if (this.classMap.get(pojoName) != null) {
      return true;
    }
    return false;
  }

  public FaClassConf getClassConf(String pojoName) {
    return this.classMap.get(pojoName);
  }

  public boolean isBreakPoint(String className, int rowNum) {
    if (this.breakPoints == null || this.breakPoints.isEmpty()) {
      return false;
    }
    Boolean isTrue = this.breakPoints.get(className + "-" + rowNum);
    if (isTrue == null || !isTrue) {
      return false;
    }
    return true;
  }

  public void beginBreakPoint() {
    this.breakPointCtrl = BREAK_STEP_CONTINUE;
    this.breakThreadId = Thread.currentThread().getId();
  }

  public void overBreakPoint() {
    this.breakThreadId = 0;
  }

  // 判断是否进入debug
  public void toBreakPointing(SageStone st, CodeLevel lvl, int rowNum) {
    if (this.breakThreadId == 0 || this.breakThreadId != Thread.currentThread().getId()) { // || lvl.sageBiz == null
      return;
    }
    AC.log("[fa-platform]is break sleep....");
    final int maxMs = 10 * 60 * 30;
    isSleeping = true;
    this.debugSage = st;
    // lvl.sageBiz.sendDebugSleepMsg(st.getFaClassName(), lvl, rowNum);
    for (int i = 0; i < maxMs; i++) {
      if (!isSleeping) {
        break;
      }
      try {
        Thread.sleep(100);
      } catch (Exception ex) {
      }
    }
    AC.log("[fa-platform]break sleep over !");
    this.debugSage = null;
  }

  public void stepOver() {
    isSleeping = false;
    breakPointCtrl = BREAK_STEP_OVER;
  }

  public void stepContinue() {
    isSleeping = false;
    breakPointCtrl = BREAK_STEP_CONTINUE;
  }

  public void stepInfo() {
  }

  public void stepOut() {
  }

  // 结束运行
  public void debugStop() {
    isSleeping = false;
    breakPointCtrl = BREAK_STOP;
  }

  // 获取变量信息
  public Map<String, FaValue> getDebugParam(String varName) {
    if (this.debugSage == null) {
      return null;
    }
    if ("this".equals(varName)) {
      return this.debugSage.get_this().getVars();
    } else {
      return this.debugSage.getVars();
    }
  }

  private boolean initClassConf() throws FaException {
    // sage类初始化
    for (FaClassConf fc : this.classMap.values()) {
      try {
        fc.setPlat(this);
        fc.initStatic();
        if (fc.getInner() != null) {
          for (FaClassConf conf : fc.getInner().values()) {
            conf.setPlat(this);
          }
        }
      } catch (Exception ex) {
        AC.log("[fa-error]", fc.getName() + ".initStatic-error");
        ex.printStackTrace();
      }
    }
    // if fc.isCtrl()
    // this.initCtrlPath(fc)
    // 初始化springBean
    for (Entry<String, FaClassConf> entry : this.classMap.entrySet()) {
      FaClassConf fa = entry.getValue();
      if (fa.isCtrl()) {
        this.initCtrlPath(fa, null);
      } else if (fa.isSpringBean()) {
        initBean(fa);
      }
    }
    // 注解bean
    for (Entry<String, FaClassConf> entry : this.classMap.entrySet()) {
      FaClassConf fa = entry.getValue();
      if (fa.isSpringBean()) {
        autowiredBean(fa);
      }
    }
    return true;
  }

  private void initDoingList() {
    doingList = new ArrayList<>();
    try {
      Set<Class<?>> classes = ToolClass.getClassList("net.alche.fa.core.doing", FaAnnoDoing.class);
      for (Class<?> cla : classes) {
        Object obj = cla.getDeclaredConstructor().newInstance();
        AbstractDoing ad = (AbstractDoing) obj;
        ad.setPlat(this);
        doingList.add(ad);
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  // 是否泛型
  public boolean isWidlyType(String typeWord) {
    if (Arrays.binarySearch(this.widlyTypes, typeWord) >= 0) {
      return true;
    }
    return false;
  }

  public CtrlNode getCtrl(String path, String httpType) throws FaException {
    if (rootNode == null) {
      throw new FaException(FaException.NULL_ERROR, "ctrl not root path node");
    }
    if (path.startsWith("/"))
      path = path.substring(1);
    String[] pps = path.split("/");
    return getCtrlDeep(rootNode.childList, httpType, pps, 0);
  }

  public CtrlNode getCtrlDeep(List<CtrlNode> nodes, String httpType, String[] pps, int ppsIndex) {
    if (!AC.empty(nodes)) {
      for (CtrlNode nn : nodes) {
        if (nn.httpType.equals(httpType) || nn.httpType.equals("")) {
          if (nn.nodeName.equals(pps[ppsIndex])) {
            if (ppsIndex + 1 == pps.length) {
              return nn;
            }
            CtrlNode rb = this.getCtrlDeep(nn.childList, httpType, pps, ppsIndex + 1);
            if (rb != null)
              return rb;
          } else if (nn.nodeName.equals("{*}") || nn.nodeName.equals("**")) {
            return nn;
          }
        }
      }
    }
    return null;
  }

  public FaClass getFaBean(String className) {
    return sprBeanMap.get(className);
  }

  private FaClass initBean(FaClassConf facc) throws FaException {
    FaClass fac = facc.instance(null);
    fac = facc.instance(null);
    sprBeanMap.put(facc.getName(), fac);
    return fac;
  }

  public void setJavaBean(String name, Object obj) {
    sprJavaMap.put(name, obj);
  }

  private void autowiredBean(FaClassConf fa) throws FaException {
    FaClass fc = sprBeanMap.get(fa.getName());
    if (fc == null || fc.getConf() == null || fc.getThisSage() == null) {
      return;
    }
    if (fa.isFatisDao() && this.fatis != null) {
      try {
        fc.call("setFatis", FaUtil.pars(fatis), FaUtil.newCodeLvl());
      } catch (Exception ex) {
        ex.printStackTrace();
      }
    }
    Map<String, FaValue> vars = fc.getThisSage().getVars();
    for (String varName : vars.keySet()) {
      if ("1".equals(fc.getConf().getVarAtTxt(varName, "FaBean"))) {
        FaValue fav = vars.get(varName);
        FaClass fv = sprBeanMap.get(fav.getObjType());
        if (fv != null) {
          fav.setSageValue(fv.getThisSage());
          fav.setNull(false);
        } else {
          Object obj = sprJavaMap.get(fav.getObjType());
          if (obj != null) {
            fav.setType(FaValue._OBJECT);
            fav.setNull(false);
            fav.setObjValue(obj);
          } else {
            AC.log("autowired 失败", fav.getObjType(), varName);
          }
        }
      }
    }
  }

  private void initCtrlPath(FaClassConf fa, FaClass fac) throws FaException {
    String parPath = fa.getAtTxt("FaBean.value");
    if (fac == null) {
      fac = initBean(fa);
    }
    if (parPath.endsWith("/*")) {
      parPath = parPath.substring(0, parPath.length() - 2);
    }
    for (FaMethod method : fa.getMethodList()) {
      String url = "";
      String type = "";
      if (method.getDef() == null) {
        continue;
      }
      url = method.getAtTxt("FaBean.value");
      type = method.getAtTxt("FaBean.type");
      if (type.equals("delete")) {
        type = "del";
      }
      // type = type.toUpperCase();
      if (!parPath.endsWith("/") && !url.startsWith("/")) {
        url = "/" + url;
      }
      String path = parPath + url;
      if (url.equals("/*index*")) {
        path = "*index*";
      }
      
      if (path.startsWith("/")) {
        path = path.substring(1);
      }
      
      String[] pnode = path.split("/");
      if (rootNode == null) {
        rootNode = new CtrlNode();
        rootNode.nodeName = "*";
      }
      fillNode(pnode, rootNode, 0, type, fac, method, path);
    }
  }

  private void fillNode(String[] pps, CtrlNode node, int index, String httpType, FaClass faClass, FaMethod faMethod,
      String path) {
    if (pps.length == index) {
      node.fac = faClass;
      node.method = faMethod;
      node.path = path;
      return;
    }
    String nName = pps[index];
    if (nName.startsWith("{")) {
      nName = "{*}";
    }
    CtrlNode cNode = null;
    if (node.childList != null) {
      for (CtrlNode n : node.childList) {
        if (n.httpType.equals(httpType) && n.nodeName.equals(nName)) {
          cNode = n;
          break;
        }
      }
    }
    if (cNode != null && pps.length <= index + 1 && !cNode.httpType.equals(httpType)) {
      cNode = null;
    }
    if (cNode == null) {
      if (node.childList == null) {
        node.childList = new ArrayList<>();
      }
      cNode = new CtrlNode();
      cNode.nodeName = nName;
      cNode.httpType = httpType;
      node.childList.add(cNode);
    }
    fillNode(pps, cNode, index + 1, httpType, faClass, faMethod, path);
  }

  // public void recurLogPath(String parPath, CtrlNode node) {
  // if (node != null && node.childList != null) {
  // for (CtrlNode item : node.childList) {
  // if (item.fac != null && item.method != null) {
  // }
  // // AC.log("recurLogPath", item.httpType + " :: " + parPath + "/" +
  // item.nodeName
  // // + " " + item.fac.getConf().getName() + "." + item.method.getMethodName())
  // this.recurLogPath(parPath + "/" + item.nodeName, item);
  // }
  // }
  // }

  public void fixFa() {
    for (FaClassConf fcc : this.getClassMap().values()) {
      this.fixMethodTypeChar(fcc);
    }
  }

  // 修复方法参数类型Char
  public void fixMethodTypeChar(FaClassConf fcc) {
    if (!AC.empty(fcc.getMethodList())) {
      for (FaMethod fm : fcc.getMethodList()) {
        if (!AC.empty(fm.getParamList())) {
          for (FaParam fp : fm.getParamList()) {
            fp.type = FaValue.getChar(fp.objTypeName);
            if (fp.type == FaValue._OBJECT) {
              if (fp.objTypeName.endsWith("...")) {
                fp.objTypeName = fp.objTypeName.substring(0, fp.objTypeName.length() - 3);
                fp.isDynamic = true;
              }
              if (this.isSageName(fp.objTypeName)) {
                fp.type = FaValue._SAGE;
              }
            }
          }
        }
      }
    }
    if (fcc.getInner() != null) {
      for (FaClassConf conf : fcc.getInner().values()) {
        this.fixMethodTypeChar(conf);
      }
    }
  }

  // 更新运行代码
  public void updateFlass(String className, String flassJson) throws FaException {
    FavaParseLogicV2 v2 = new FavaParseLogicV2();
    FaClassConf fcc = v2.parseJson(className, flassJson);
    FaClassConf conf = this.classMap.get(fcc.getName());
    if (conf == null) {
      this.fixMethodTypeChar(fcc);
      this.classMap.put(fcc.getName(), fcc);
      fcc.setPlat(this);
      fcc.initStatic();
      if (fcc.isCtrl()) {
        this.initCtrlPath(fcc, null);
      } else if (fcc.isSpringBean()) {
        this.initBean(fcc);
        this.autowiredBean(fcc);
      }
      return;
    } else {
      conf.def = fcc.def;
      conf.varDef = fcc.varDef;
      conf.methodList = fcc.methodList;
      conf.extendName = fcc.extendName;
      conf.implNameList = fcc.implNameList;
      conf.initLogicList = fcc.initLogicList;
      conf.staticLogicList = fcc.staticLogicList;
      conf.inner = fcc.inner;
      if (conf.staticSage != null) {
        conf.staticSage.run(conf.staticLogicList, null);
      }
      this.fixMethodTypeChar(conf);
    }
    fcc.setPlat(this);
    if (fcc.getInner() != null) {
      for (FaClassConf icc : fcc.getInner().values()) {
        icc.setPlat(this);
      }
    }
    FaClass fc = this.sprBeanMap.get(conf.getName());
    if (fc != null) {
      fc.conf = conf;
      fc.getThisSage().run(conf.initLogicList, null);
      this.autowiredBean(conf);
      if (conf.isCtrl()) {
        this.initCtrlPath(conf, fc);
      }
    } else if (conf.isCtrl()) {
      this.initCtrlPath(fcc, null);
    } else if (fcc.isSpringBean()) {
      this.initBean(fcc);
      this.autowiredBean(fcc);
    }
  }

  // public String getInnterType(String className)
  // if className == null
  // return ""
  // if className.endsWith("[]")
  // className = className.substring(0, className.length() - 2 )
  // return className
  public String getInnterType(String type) {
    if (type.endsWith("[]")) {
      return type.substring(0, type.length() - 2);
    }
    if (type.contains("<")) {
      if (type.contains(",")) {
        type = type.substring(type.indexOf(",") + 1, type.length() - 1);
      } else {
        type = type.substring(type.indexOf("<") + 1, type.length() - 1);
      }
    }
    return type;
  }

  public boolean isInnterType(String type) {
    if (type.contains("[") || type.contains("<") || type.contains(",") || type.contains(".")) {
      return false;
    }
    return true;
  }

  public static boolean isChar(String val) {
    return "char".equals(val) || "Character".equals(val);
  }

  public static boolean isByte(String val) {
    return "byte".equals(val) || "Byte".equals(val);
  }

  public static boolean isShort(String val) {
    return "short".equals(val) || "Short".equals(val);
  }

  public static boolean isInt(String val) {
    return "int".equals(val) || "Integer".equals(val);
  }

  public static boolean isLong(String val) {
    return "long".equals(val) || "Long".equals(val);
  }

  public static boolean isFloat(String val) {
    return "float".equals(val) || "Float".equals(val);
  }

  public static boolean isDouble(String val) {
    return "double".equals(val) || "Double".equals(val);
  }

  public static boolean isBoolean(String val) {
    return "boolean".equals(val) || "Boolean".equals(val);
  }

  public FaCoreConf getConf() {
    return this.conf;
  }

  public void setConf(FaCoreConf conf) {
    this.conf = conf;
  }

  public Map<String, FaClassConf> getClassMap() {
    return this.classMap;
  }

  public void setClassMap(Map<String, FaClassConf> classMap) {
    this.classMap = classMap;
  }

  public Map<String, String> getObjPath() {
    return this.objPath;
  }

  public void setObjPath(Map<String, String> objPath) {
    this.objPath = objPath;
  }

  public Map<String, Class> getObjClasses() {
    return this.objClasses;
  }

  public void setObjClasses(Map<String, Class> objClasses) {
    this.objClasses = objClasses;
    if (this.objClassesTmp != null && this.objClasses != null) {
      for (String key : this.objClassesTmp.keySet()) {
        this.objClasses.put(key, this.objClassesTmp.get(key));
      }
    }
  }

  public Map<String, FaValue> getStaticFa() {
    return this.staticFa;
  }

  public void setStaticFa(Map<String, FaValue> staticFa) {
    this.staticFa = staticFa;
  }

  public Map<String, BreakPoint> getBpMap() {
    return this.bpMap;
  }

  public void setBpMap(Map<String, BreakPoint> bpMap) {
    this.bpMap = bpMap;
  }

  public Map<String, FaClass> getSprBeanMap() {
    return this.sprBeanMap;
  }

  public void setSprBeanMap(Map<String, FaClass> sprBeanMap) {
    this.sprBeanMap = sprBeanMap;
  }

  public CtrlNode getRootNode() {
    return this.rootNode;
  }

  public void setRootNode(CtrlNode rootNode) {
    this.rootNode = rootNode;
  }

  public List<AbstractDoing> getDoingList() {
    return this.doingList;
  }

  public void setDoingList(List<AbstractDoing> doingList) {
    this.doingList = doingList;
  }

  public SqlDo getSqlDo() {
    return this.sqlDo;
  }

  public void setSqlDo(SqlDo sqlDo) {
    this.sqlDo = sqlDo;
  }

  public FaDo getFaDo() {
    return this.faDo;
  }

  public void setFaDo(FaDo faDo) {
    this.faDo = faDo;
  }

  public Map<String, Boolean> getBreakPoints() {
    return this.breakPoints;
  }

  public void setBreakPoints(Map<String, Boolean> breakPoints) {
    this.breakPoints = breakPoints;
  }

  public SageStone getDebugSage() {
    return this.debugSage;
  }

  public void setDebugSage(SageStone debugSage) {
    this.debugSage = debugSage;
  }

  public long getBreakThreadId() {
    return this.breakThreadId;
  }

  public void setBreakThreadId(long breakThreadId) {
    this.breakThreadId = breakThreadId;
  }

  public int getBreakPointCtrl() {
    return this.breakPointCtrl;
  }

  public void setBreakPointCtrl(int breakPointCtrl) {
    this.breakPointCtrl = breakPointCtrl;
  }

  public boolean getIsSleeping() {
    return this.isSleeping;
  }

  public void setIsSleeping(boolean isSleeping) {
    this.isSleeping = isSleeping;
  }

  public boolean getIsDebug() {
    return this.isDebug;
  }

  public void setIsDebug(boolean isDebug) {
    this.isDebug = isDebug;
  }

  public void setFatis(Object obj) {
    this.fatis = obj;
  }

  public void putJavaClass(String name, Class cls) {
    if (this.objClasses != null) {
      this.objClasses.put(name, cls);
    } else {
      if (this.objClassesTmp == null) {
        this.objClassesTmp = new HashMap<>();
      }
      this.objClassesTmp.put(name, cls);
    }
  }
}